#ifndef A8_MYSQL_H
#define A8_MYSQL_H

#include <string>
#include <mysql.h>
#include <assert.h>
#include <a8/xvalue.hpp>

namespace a8
{
    namespace mysql
    {
		class Connection
        {
        private:
            MYSQL *mConn;
            std::string mHost;
            int mPort;
            std::string mUserName;
            std::string mPwd;
            std::string mDataBase;
            bool mConnected;
        public:
            Connection():mConn(NULL),
                         mHost(""),
                         mPort(0),
                         mUserName(""),
                         mPwd(""),
                         mDataBase(""),
                         mConnected(false)
            {
                mConn = mysql_init(NULL);
                assert(mConn);
            }
	
            ~Connection()
            {
                if(mConn){
                    mysql_close(mConn);
				}
            }

            MYSQL* Conn() 
            { 
                return mConn; 
            }

            bool Connected() 
            {	 
                return mConnected; 
            }

            bool Connect(const std::string& host, int port, 
                         const std::string& userName, const std::string& pwd, const std::string& dataBase)
            {
                mHost = host;
                mPort = port;
                mUserName = userName;
                mPwd = pwd;
                mDataBase = dataBase;
                return mysql_real_connect(mConn, mHost.c_str(), mUserName.c_str(), mPwd.c_str(), mDataBase.c_str(), mPort, NULL, 0) != NULL;
            }

			bool Connect()
			{
				return mysql_real_connect(mConn, mHost.c_str(), mUserName.c_str(), mPwd.c_str(), mDataBase.c_str(), mPort, NULL, 0) != NULL;
			}

			bool ReConnect()
			{
				assert(false);
				return false;
			}

            void Close()
            {
				if(mConn){
					mysql_close(mConn);
				}
            }

            int GetErrNo()
            {
                return mysql_errno(mConn);
            }

            std::string GetError()
            {
                std::string strError = mysql_error(mConn);
                return strError;
            }
        };

        class Query
        { 
        private:
            Connection *mConnection;
            MYSQL_RES *mMysqlRes;
            MYSQL_ROW mRow;
            MYSQL_FIELD *mFileds;
			unsigned long *mFieldsLen;

			void FreeLastDataSet()
			{
				if(mMysqlRes){
					mysql_free_result(mMysqlRes);
				}
				mMysqlRes = NULL;
				mRow = NULL;
			}
        public:
            Query(Connection *conn = NULL):
                mConnection(conn),
                mMysqlRes(NULL),
                mRow(NULL),
                mFileds(NULL)
            {
            }

            ~Query() 
            {
				FreeLastDataSet();
            }

			a8::mysql::Connection* GetConnection() 
            { 
                return mConnection;
            }

            void SetConnection(a8::mysql::Connection *connection) 
            { 
                mConnection = connection; 
            }

            int ExecQuery(const char* queryStr)
            {
				FreeLastDataSet();
                int err = mysql_query(mConnection->Conn(), queryStr);
				if(err == 0){
					mMysqlRes = mysql_store_result(mConnection->Conn());
					if(RowsNum() > 0){
						Next();
					}
					return RowsNum();
				}
				return -1;
            }
			
			int ExecQuery(const std::string& querystr)
			{
				return ExecQuery((char*)querystr.c_str());
			}

            bool ExecScript(const char* scriptStr)
            {
				FreeLastDataSet();
                return mysql_query(mConnection->Conn(), scriptStr) == 0;
            }

            bool ExecScript(const std::string& scriptStr)
			{
				return ExecScript(scriptStr.c_str());
			}

            int RowsNum()
            {
				assert(mMysqlRes);
                return mMysqlRes ? mysql_num_rows(mMysqlRes) : 0;
            }

			int FieldsNum()
			{
				assert(mMysqlRes);
                return mMysqlRes ? mysql_num_fields(mMysqlRes) : 0;
			}

            bool Eof()
            {
				assert(mMysqlRes);
                return mRow == NULL;
            }

			void First()
			{
				assert(mMysqlRes);
				if(mMysqlRes && RowsNum() > 0){
					mysql_data_seek(mMysqlRes, 0);
					mRow = mysql_fetch_row(mMysqlRes);
					mFieldsLen = mysql_fetch_lengths(mMysqlRes);
				}
			}

			void Prev()
			{
				assert(mMysqlRes);
				if(mMysqlRes){
					mysql_row_seek(mMysqlRes, mysql_row_tell(mMysqlRes) - 1);
					mRow = mysql_fetch_row(mMysqlRes);
					mFieldsLen = mysql_fetch_lengths(mMysqlRes);
				}
			}

			void Next()
			{
				assert(mMysqlRes);
				if(mMysqlRes){
					mRow = mysql_fetch_row(mMysqlRes);
					mFieldsLen = mysql_fetch_lengths(mMysqlRes);
				}
			}

			void Last()
			{
				assert(mMysqlRes);
				if(mMysqlRes && RowsNum() > 0){
					mysql_data_seek(mMysqlRes, RowsNum() - 1);
					mRow = mysql_fetch_row(mMysqlRes);
					mFieldsLen = mysql_fetch_lengths(mMysqlRes);
				}
			}
			
			a8::XValue GetValue(short idx)
			{
				a8::XValue xv;
				if(mRow && mRow[idx]){
					if(mFieldsLen[idx] > 0){
						xv.SetDynData((const char*)mRow[idx], mFieldsLen[idx]);
					}else{
						xv.Set((const char*)mRow[idx]);
					}
				}
				return xv;
			}

			a8::XValue GetValue(const std::string& fieldname)
			{
				if(mMysqlRes){
					int fieldsNum = FieldsNum();
					mysql_field_seek(mMysqlRes, 0);
					for(int i = 0; i < fieldsNum; i++){
						MYSQL_FIELD *field = mysql_fetch_field(mMysqlRes);
						if (field != NULL && strcmp(field->name, fieldname.c_str()) == 0 ) {
							return GetValue(i);
						}
					}//end for
				}
				return a8::XValue();
			}

			std::string formatsql(const char* fmt, a8::XValue* args, int argnum)
			{
				std::string result;
				result.reserve(1024);
				int curarg = 0;
				const char *p = fmt;
				while(*p){
					if(*p == '%' && *(p+1)){
						p++;
						switch(*p){
						case 'd':
							result.append(args[curarg++].GetString().c_str());
							break;
						case 'f':
							result.append(args[curarg++].GetString().c_str());
							break;
						case 's':
							{
								std::string str = args[curarg++].GetString();
								char* p = new char[str.size() * 2 + 1];
								p[0] = '\0';
								mysql_real_escape_string(mConnection->Conn(), p, str.data(), str.size());
								result.append(p);
								delete [] p;
								break;
							}
						default:
							{
								result.push_back('%');
								result.push_back(*p);
							}
						}
					}else{
						result.push_back(*p);
					}
					p++;
				}
				return result;
			}

#include <a8/inc/mysql.inl>
        };
    }
}

#endif
